package com.mico.echart.monitor.system;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.text.DecimalFormat;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author micocube
 * projectName: utils4j
 * packageName: com.coding.system
 * email: ldscube@gmail.com
 * createTime: 2019-12-27 16:52
 * version: 0.1
 * description: jvm内存信息
 */
public class MemoryInformation {

    /**
     *
     * usedMemory 是heap使用内存 (eden+survivor+old)
     */
    private final long m_usedMemory;

    /**
     * maxMemory 是heap最大内存
     */
    private final long m_maxMemory;

    /**
     * usedOldGen "Old Gen"使用内存
     */
    private final long m_usedOldGen;

    /**
     * maxOldGen "Old Gen"最大内存
     */
    private final long m_maxOldGen;

    /**
     * usedPermGen "Perm Gen"使用内存
     */
    private final long m_usedMetaspace;

    /**
     * maxPermGen "Perm Gen"最大内存
     */
    private final long m_maxMetaspace;

    /**
     * usedEdenSpace "Eden Space"使用内存
     */
    private final long m_usedEdenSpace;

    /**
     * maxEdenSpace "Eden Space"最大内存
     */
    private final long m_maxEdenSpace;

    /**
     * usedSurvivorSpace "Survivor Space"使用内存
     */
    private final long m_usedSurvivorSpace;

    /**
     * maxSurvivorSpace "Survivor Space"最大内存
     */
    private final long m_maxSurvivorSpace;

    private final long m_usedNonHeapMemory;

    private final long m_maxNonHeapMemory;

    private MBeanServer m_mbeanServer = ManagementFactory.getPlatformMBeanServer();

    private static final String DIRECT_BUFFER_MBEAN = "java.nio:type=BufferPool,name=direct";

    private static final String MAPPED_BUFFER_MBEAN = "java.nio:type=BufferPool,name=mapped";

    private static final DecimalFormat df = new DecimalFormat("#0.00");



    public Map<String, String> collect(){

        Map<String, String> map = new LinkedHashMap<String, String>();
        map.put("jvm.heap.used",FileSize.formatFileSize(m_usedMemory));
        map.put("jvm.heap.max",FileSize.formatFileSize(m_maxMemory));
        map.put("jvm.heap.used.percent",df.format(getUsedMemoryPercentage())+"%");

        map.put("jvm.oldGen.used",FileSize.formatFileSize(m_usedOldGen));
        map.put("jvm.oldGen.max",FileSize.formatFileSize(m_maxOldGen));
        map.put("jvm.oldGen.used.percent",df.format(getUsedOldGenPercentage())+"%");

        map.put("jvm.metaspace.used",FileSize.formatFileSize(m_usedMetaspace));
        map.put("jvm.metaspace.max",FileSize.formatFileSize(m_maxMetaspace));
        map.put("jvm.metaspace.used.percent",df.format(getMetaspacePercentage())+"%");

        map.put("jvm.edenSpace.used",FileSize.formatFileSize(m_usedEdenSpace));
        map.put("jvm.edenSpace.max",FileSize.formatFileSize(m_maxEdenSpace));
        map.put("jvm.edenSpace.used.percent",df.format(getUsedEdenSpacePercentage())+"%");

        map.put("jvm.survivorSpace.used",FileSize.formatFileSize(m_usedSurvivorSpace));
        map.put("jvm.survivorSpace.max",FileSize.formatFileSize(m_maxSurvivorSpace));
        map.put("jvm.survivorSpace.used.percent",df.format(getUsedSurvivorSpacePercentage())+"%");

        map.put("jvm.nonHeapMemory.used",FileSize.formatFileSize(m_usedNonHeapMemory));
        map.put("jvm.nonHeapMemory.max",FileSize.formatFileSize(m_maxNonHeapMemory));
        map.put("jvm.nonHeapMemory.used.percent",df.format(getUsedNonHeapPercentage())+"%");

        map.put("jvm.usedDirectBufferSize",FileSize.formatFileSize(getUsedDirectBufferSize()));
        map.put("jvm.usedMappedSize",FileSize.formatFileSize(getUsedMappedSize()));
        return map;

    }

    public MemoryInformation() {
        m_usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        m_maxMemory = Runtime.getRuntime().maxMemory();
        final MemoryPoolMXBean metaspaceMemoryPool = getMetaspaceMemoryPool();
        if (metaspaceMemoryPool != null) {
            final MemoryUsage usage = metaspaceMemoryPool.getUsage();
            m_usedMetaspace = usage.getUsed();
            m_maxMetaspace = usage.getMax();
        } else {
            m_usedMetaspace = 0;
            m_maxMetaspace = 0;
        }
        final MemoryPoolMXBean oldGenMemoryPool = getOldGenMemoryPool();
        if (oldGenMemoryPool != null) {
            final MemoryUsage usage = oldGenMemoryPool.getUsage();
            m_usedOldGen = usage.getUsed();
            m_maxOldGen = usage.getMax();
        } else {
            m_usedOldGen = 0;
            m_maxOldGen = 0;
        }

        final MemoryPoolMXBean edenSpaceMemoryPool = getEdenSpacePool();
        if (edenSpaceMemoryPool != null) {
            final MemoryUsage usage = edenSpaceMemoryPool.getUsage();
            m_usedEdenSpace = usage.getUsed();
            m_maxEdenSpace = usage.getMax();
        } else {
            m_usedEdenSpace = 0;
            m_maxEdenSpace = 0;
        }

        final MemoryPoolMXBean survivorSpacePool = getSurvivorSpaceMemoryPool();
        if (survivorSpacePool != null) {
            final MemoryUsage usage = survivorSpacePool.getUsage();
            m_usedSurvivorSpace = usage.getUsed();
            m_maxSurvivorSpace = usage.getMax();
        } else {
            m_usedSurvivorSpace = 0;
            m_maxSurvivorSpace = 0;
        }

        final MemoryUsage nonHeapMemoryUsage = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();

        m_usedNonHeapMemory = nonHeapMemoryUsage.getUsed();
        m_maxNonHeapMemory = nonHeapMemoryUsage.getMax();
    }

    public long getMaxEdenSpace() {
        return m_maxEdenSpace;
    }

    public long getMaxMemory() {
        return m_maxMemory;
    }

    public long getMaxNonHeapMemory() {
        return m_maxNonHeapMemory;
    }

    public long getMaxOldGen() {
        return m_maxOldGen;
    }

    public long getMaxPermGen() {
        return m_maxMetaspace;
    }

    public long getMaxSurvivorSpace() {
        return m_maxSurvivorSpace;
    }

    private MemoryPoolMXBean getEdenSpacePool() {
        for (final MemoryPoolMXBean memoryPool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (memoryPool.getName().endsWith("Eden Space")) {
                return memoryPool;
            }
        }
        return null;
    }

    private MemoryPoolMXBean getOldGenMemoryPool() {
        for (final MemoryPoolMXBean memoryPool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (memoryPool.getName().endsWith("Old Gen")) {
                return memoryPool;
            }
        }
        return null;
    }

    private MemoryPoolMXBean getMetaspaceMemoryPool() {
        for (final MemoryPoolMXBean memoryPool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (memoryPool.getName().endsWith("Metaspace")) {
                return memoryPool;
            }
        }
        return null;
    }


    private MemoryPoolMXBean getPermGenMemoryPool() {
        for (final MemoryPoolMXBean memoryPool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (memoryPool.getName().endsWith("Perm Gen")) {
                return memoryPool;
            }
        }
        return null;
    }



    private MemoryPoolMXBean getSurvivorSpaceMemoryPool() {
        for (final MemoryPoolMXBean memoryPool : ManagementFactory.getMemoryPoolMXBeans()) {
            if (memoryPool.getName().endsWith("Survivor Space")) {
                return memoryPool;
            }
        }
        return null;
    }

    public long getUsedDirectBufferSize() {
        return getUsed(DIRECT_BUFFER_MBEAN);
    }

    public long getUsedEdenSpace() {
        return m_usedEdenSpace;
    }

    public double getUsedEdenSpacePercentage() {
        if (m_usedEdenSpace > 0 && m_maxEdenSpace > 0) {
            return 100d * m_usedEdenSpace / m_maxEdenSpace;
        }
        return 100d;
    }

    public long getUsedMappedSize() {
        return getUsed(MAPPED_BUFFER_MBEAN);
    }

    private long getUsed(String mappedBufferMbean) {
        long mappedBufferSize = 0;
        try {
            ObjectName directPool = new ObjectName(mappedBufferMbean);
            mappedBufferSize = (Long) m_mbeanServer.getAttribute(directPool, "MemoryUsed");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mappedBufferSize;
    }

    public long getUsedMemory() {
        return m_usedMemory;
    }

    public double getUsedMemoryPercentage() {
        if(m_maxMemory==-1){
            return 100;
        }
        return 100d * m_usedMemory / m_maxMemory;
    }

    public long getUsedNonHeapMemory() {
        return m_usedNonHeapMemory;
    }

    public double getUsedNonHeapPercentage() {
        if (m_usedNonHeapMemory > 0 && m_maxNonHeapMemory > 0) {
            return 100d * m_usedNonHeapMemory / m_maxNonHeapMemory;
        }
        return 100d;
    }

    public long getUsedOldGen() {
        return m_usedOldGen;
    }

    public double getUsedOldGenPercentage() {
        if (m_usedOldGen > 0 && m_maxOldGen > 0) {
            return 100d * m_usedOldGen / m_maxOldGen;
        }
        return 100d;
    }

    public long getUsedPermGen() {
        return m_usedMetaspace;
    }

    public double getMetaspacePercentage() {
        if (m_usedMetaspace > 0 && m_maxMetaspace > 0) {
            return 100d * m_usedMetaspace / m_maxMetaspace;
        }
        return 100d;
    }

    public long getUsedSurvivorSpace() {
        return m_usedSurvivorSpace;
    }

    public double getUsedSurvivorSpacePercentage() {
        if (m_usedSurvivorSpace > 0 && m_maxSurvivorSpace > 0) {
            return 100d * m_usedSurvivorSpace / m_maxSurvivorSpace;
        }
        return 100d;
    }




    @Override
    public String toString() {
        return getClass().getSimpleName() + 
                "[usedMemory=" + getUsedMemory() + 
                ", maxMemory=" + getMaxMemory() + ']';
    }

}
